/*
 
 This is a cache simulation tool created by Kyle Roden at the University of Alabama in Huntsville.
 
 The user specifies parameters for various caches and then runs the tool to analyze the execution of any program. After the completion of the program, the statistics on cache accesses can be found in kylecache.out.
 
 The caches are stored as arrays of 64 bit integers, where each row consists of a cache set divided into blocks.  Because this is a simulation, there is no need to store the actual data. Only tags are stored. 
 
 This code is derived from code copyright by Intel and is provided with their open source Pin package.  Very little original Intel is present in this file, but I will include their copyright information anyway.
 
 
 http://www.pintool.org/
 
 Pin is required to use this program. But it's open source! Use the link above to get pin.
 
 
 **Compile instructions
 You need the makefile that should be in the same directory as this source.
 
 use: "make PIN_HOME=<top level directory where pin is installed>"
 
 or simply "make" if pin is at /pin
 
 **Execution
 $<path-to-pin>/pin -t <path-to-this-folder>/obj-intel64/KyleCache.so -- <path to target executable> [target exe arguments]
 
 */ 

/*   
 
 Intel Open Source License 
 
 Copyright (c) 2002-2011 Intel Corporation. All rights reserved.
 
 Redistribution and use in source and binary forms, with or without
 modification, are permitted provided that the following conditions are
 met:
 
 Redistributions of source code must retain the above copyright notice,
 this list of conditions and the following disclaimer.  Redistributions
 in binary form must reproduce the above copyright notice, this list of
 conditions and the following disclaimer in the documentation and/or
 other materials provided with the distribution.  Neither the name of
 the Intel Corporation nor the names of its contributors may be used to
 endorse or promote products derived from this software without
 specific prior written permission.
 
 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE INTEL OR
 ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 END_LEGAL 
 
 */

#include <stdio.h>
#include <stdlib.h>
#include "pin.H"
#include <iostream>
#include <fstream>
#include <string.h>
#include <deque>
#include <list>
#include <set>
#include "enums.h"
#include "Cache.h"

///////Command line switches/////

//input file
KNOB<string> KnobInputFiles(KNOB_MODE_WRITEONCE,"pintool",
                            "i","sample.config", 
                            "specify input files");
//debug flag
KNOB<string> KnobDebug(KNOB_MODE_WRITEONCE,"pintool",
                            "d","false", 
                            "turn on debugging");

//no ins flag
KNOB<string> KnobInsCache(KNOB_MODE_WRITEONCE,"pintool",
                       "inscache","on", 
                       "turn off instruction cache");

//debug flag
KNOB<INT64> KnobDebugStop(KNOB_MODE_WRITEONCE,"pintool",
                       "inscount","0", 
                       "set last instr # for debugging");


static INT64 icount = 0; //total count of instructions
static UINT64 rcount = 0; //count of data reads
static UINT64 wcount = 0; //count of data writes
int numConfigs; //holds the number of configuraiton files
ofstream results, debug;//files to hold results

PIN_LOCK sharedCacheLock;


/*
 two arrays of pointers to firs tlevel caches.
 position 0 is the first level cache of the first core,
 position 1 is the first level cache of the second core,
 and so on.
 */
Cache ** insCacheList;
Cache ** dataCacheList;
int coreCount;
bool instructions;
int debugLimit;

/*
 This is the function used to read config files.
  */
void readInputFile(string filename);



//verifys certain input paramters are powers of 2 and returns number, otherwise exits.
int check2(int num){
    
    if (!((num > 0) && ((num & (num - 1)) == 0))){
        cout <<endl<<"I dont like this: "<<num<<endl;
        cout << "invalid config data.  all block sizes, cache sizes,"
        <<"and associativities must be power of 2. exiting.";
        exit(2222);
    }
    return num;
}
/*
 This function will print the Cache Configs... useful for debugging. must use the -p 
 flag at command line to get this data
 */
void printCacheConfigs();

// called for every instruction fetch
VOID everyInstr(VOID *ip,THREADID threadid)
{ 
    icount++;
    
    if(instructions)
        insCacheList[threadid%coreCount]->searchCache(UINT64(ip),threadid,0,debug,icount);
 
}

// called for every memory read operation. 
VOID RecordMemRead(VOID * ip, VOID * addr,THREADID threadid)
{
    rcount++;
    
    dataCacheList[threadid%coreCount]->searchCache(UINT64(addr),threadid,0,debug,icount);
       
}
    
// called for each memory write operation. 
VOID RecordMemWrite(VOID * ip, VOID * addr,THREADID threadid)
{
    wcount++;
    
    dataCacheList[threadid%coreCount]->searchCache(UINT64(addr),threadid,1,debug,icount);
        
    
       
}

// Is called for every instruction and instruments reads and writes
VOID Instruction(INS ins, VOID *v)
{
    // Instruments memory accesses using a predicated call, i.e.
    // the instrumentation is called iff the instruction will actually be executed.
    //
    // The IA-64 architecture has explicitly predicated instructions. 
    // On the IA-32 and Intel(R) 64 architectures conditional moves and REP 
    // prefixed instructions appear as predicated instructions in Pin.
    UINT32 memOperands = INS_MemoryOperandCount(ins);
        
    

    //instrument all instructions
    INS_InsertCall(ins, IPOINT_BEFORE, (AFUNPTR)everyInstr, IARG_INST_PTR, IARG_THREAD_ID,IARG_END);
    
    // Iterate over each memory operand of the instruction.
    for (UINT32 memOp = 0; memOp < memOperands; memOp++)
    {
        if (INS_MemoryOperandIsRead(ins, memOp))
        {
            INS_InsertPredicatedCall(
                                     ins, IPOINT_BEFORE, (AFUNPTR)RecordMemRead,
                                     IARG_INST_PTR,
                                     IARG_MEMORYOP_EA, memOp,
                                     IARG_THREAD_ID,
                                     IARG_END);
        }
        // Note that in some architectures a single memory operand can be 
        // both read and written (for instance incl (%eax) on IA-32)
        // In that case we instrument it once for read and once for write.
        if (INS_MemoryOperandIsWritten(ins, memOp))
        {
            INS_InsertPredicatedCall(
                                     ins, IPOINT_BEFORE, (AFUNPTR)RecordMemWrite,
                                     IARG_INST_PTR,
                                     IARG_MEMORYOP_EA, memOp,
                                     IARG_THREAD_ID,
                                     IARG_END);
        }	
    }
}

//This function is run at program termination
VOID Fini(INT32 code, VOID *v){
    
    
    results << "Results:\n\n"<<dec;
    results << "Total instructions: "<<icount;
    results << "\nMemory read requests: "<<rcount;
    results << "\nMemory write requests: "<<wcount<<endl<<endl;
    
     set<int> processed;
     for(int j=0; j<coreCount; j++){
    
         insCacheList[j]->printStats(results, processed);
         dataCacheList[j]->printStats(results, processed);
    
     }
    
    results.close();
    
    
}

//print usage instructions
INT32 Usage()
{
    cout<< "This Pintool simulates a user defined cache and prints metrics to a file\n\n"
              <<"You seem to have entered an incorrect invocation.  The basic invocation is:\n"
              <<"(%path to pin%)/pin -t (%path to tool%)/KyleCache.so -i (input config name) -- (application)"
             << "\n Additional command line paramters (after -i) are: \n\n"
             << "-d: debugging. valid options are \"on\"(transactions) and \"dump\"(transactions and cache dump at every change). output file is called debug. default is off.\n\n"
            <<"-inscount: integer value. specify number of instructions to print debug info for. default is 1000.\n\n"
            <<"-inscache: whether to instrument instructions or not. on or off. default is on.\n\n\n ";
    
    return -1;
}


//main
int main(int argc, char *argv[])
{
    if (PIN_Init(argc, argv)) return Usage();
    
    
    results.open("results");
    debug.open("debug");
    
    string input = KnobInputFiles.Value(); //contains all input files joined by '+'
    
    
    //put an offset in the icount to turn off debugging if no debugging flag was given.
    
    if(KnobDebug.Value()=="false")
        debugLimit = 0;
    else
        debugLimit = KnobDebugStop.Value();
    
    readInputFile(input);
   
    if (KnobInsCache.Value()=="on")
        instructions=true;
    else
        instructions=false;
    
    INS_AddInstrumentFunction(Instruction, 0);
    PIN_AddFiniFunction(Fini, 0);
    
   
    
    
    
    // Never returns
    PIN_StartProgram();
    
    return 0;
}


void readInputFile(string filename){
    
    ifstream file; 
    file.open(filename.c_str());  //open specified file for input
    string current; // holds current position of file
    //Config config; // config struct to be returned
    int cacheCount; //total number of caches in this config
    deque<Cache*> cacheDeque; //temp container for caches
    
    
    if(!file){  //make sure file is open
        cout << "error opening file: " << filename <<". Exiting.";
        exit(44);
    }
    
    //Skip to core count
    do {
        getline(file, current);
        
    }
    while ( current[0]=='#' || current [0]=='\0');
    
    coreCount = atoi(current.c_str());
    
    //Skip to cache definitions
    do {
        getline(file, current);   
    }
    while ( current[0]=='#' || current [0]=='\0');
    
    
    //read cache config lines one at a time
    
    for (;  current[0]!='#' ; getline(file, current)){
        if (current[0]=='\0'){
            continue;
        }
        
        CacheInfo info; //structure holds most cache info
        Cache* cachePtr;
        int pos=0; //position in the line
        
        string temp="";
        
        //remove all whitespace from string
        temp="";
        for(unsigned int i=0; i<current.size();i++)
            if(current[i]!=' ')
                temp= temp + current[i];
        current=temp;
        
        
        //read in id, convert to int
        for( temp = ""; current[pos] !=',';pos++)
            temp += current[pos];
        info.selfID = atoi(temp.c_str());
        pos++;
        //read description
        for( temp = ""; current[pos] !=',';pos++)
            temp+= current[pos];
        info.desc = temp;
        pos++;
        
        //read parent
        for( temp = ""; current[pos] !=',';pos++)
            temp+= current[pos];
        info.parentID = atoi(temp.c_str());
        pos++;
        //read size
        for( temp = ""; current[pos] !=',';pos++)
            temp+= current[pos];
        switch (temp[temp.size()-1]){
                
            case '0': case '1': case '2': case '3': case '4': 
            case '5': case '6': case '7': case '8': case '9':
                info.size=atoi(temp.substr(0,temp.size()).c_str());
                break;
            case 'B': case 'b':
                info.size=atoi(temp.substr(0,temp.size()-1).c_str());
                break;
            case 'K': case 'k':
                info.size=atoi(temp.substr(0,temp.size()-1).c_str())*1024;
                break;
            case 'M': case 'm':
                info.size=atoi(temp.substr(0,temp.size()-1).c_str())*1024*1024;
                break;
            case 'G': case 'g':
                info.size=atoi(temp.substr(0,temp.size()-1).c_str())*1024*1024*1024;
                break;
            default:
                cout << "invalid prefix used for parameter size for cache id:"
                <<info.selfID<<"in file: "<<filename <<"...exiting..."<<endl;
                exit(45);
        }
        check2(info.size); //verify size is power of 2
        pos++;
        //read blocksize
        for( temp = ""; current[pos] !=',';pos++)
            temp+= current[pos];
        info.blockSize = check2(atoi(temp.c_str()));
        pos++;
        
        //read associativity
        for( temp = ""; current[pos] !=',';pos++)
            temp+= current[pos];
        info.assoc = check2(atoi(temp.c_str()));
        pos++;
        
        //read replacment policy
        for( temp = ""; current[pos] !='\0';pos++)
            temp+= current[pos];
        info.policy = temp;
        pos++;
        //info.coherence = coherence;
        
        if(KnobDebug.Value()=="dump")
            info.dump = true;
        info.debugLimit = debugLimit;

        cachePtr = new Cache(info);
        cacheDeque.push_back(cachePtr);
    }
    
    /* 
     Now we have the Deque full, and we know how many total Caches there are.  
     
     We now need to give each cache pointers to its parents and children.  Since all 
     we have are the user defined Id's, and our caches are possibly out of order in the 
     deque, we need to reoganize them into an ordered array
     
     */
    
    cacheCount = cacheDeque.size();
    Cache* cacheArray[cacheCount+1];//cacheArray[0] refers to main memory
    cacheArray[0]=NULL;
    
    //this loop moves the Caches from the Dequeue to an ordered array.
    for(int i=0; i<cacheCount; i++){
        cacheArray[cacheDeque[i]->getCacheInfo().selfID]=cacheDeque[i];
        if(UINT64(cacheDeque[i]->getCacheInfo().selfID)>UINT64(cacheDeque.size())){
            cout <<"sorry, you didn't number the caches properly. start at 1 and don't skip any numbers"<<endl<<endl;
            exit(350);
        }
    }
    // now that we know which cache is which, we can assign parents, children, and siblings
    
    for (int i=1; i<=cacheCount; i++){
        
        list<Cache*> tempChildrenList;
        list<Cache*> tempSiblingList;
        //set parent
        cacheArray[i]->setParent(cacheArray[cacheArray[i]->getCacheInfo().parentID]);
        
        //for each cache, look at every other cache
        for(int j=1; j<=cacheCount; j++){
            //if they share a parent, they are siblings
            if(cacheArray[j]->getCacheInfo().parentID == cacheArray[i]->getCacheInfo().parentID){
                if(j!=i){ //if not the same cache
                   tempSiblingList.push_back(cacheArray[j]);
                }
            }
            //if the i cache is parent to the j cache, add j cache to child list
            else if(cacheArray[j]->getCacheInfo().parentID == i)
                tempChildrenList.push_back(cacheArray[j]);
        }
        int numChildren = tempChildrenList.size();
        int numSiblings= tempSiblingList.size();
        Cache* children[numChildren];
        Cache* siblings[numSiblings];
        for(int k=0; k<numChildren; k++){
            children[k]=tempChildrenList.front();
            tempChildrenList.pop_front();
        }
        for(int k=0; k<numSiblings; k++){
            siblings[k]=tempSiblingList.front();
            tempSiblingList.pop_front();
        }
        
        
        cacheArray[i]->setChildren(children, numChildren);
        cacheArray[i]->setSiblings(siblings, numSiblings);
        
    }
    
    //Skip to firstCache definitions
    do getline(file, current);
    while ( current[0]=='#' || current [0]=='\0');
    
    int coreCountCheck=0;
    
    //Cache* data[config.coreCount]; //holds the list of first level data caches
    //Cache* ins[config.coreCount]; //holds the list of first level ins caches
    insCacheList = new Cache*[coreCount];
    dataCacheList = new Cache*[coreCount];
    //read the first cache specifications, one core at a time
    for (;  current[0]!='#' && !file.eof() ; getline(file, current)){
        
        if (current[0]=='\0'){
            getline(file, current);
            continue;
        }
        string temp;
        int pos = 0;
        int core, tempInsId, tempDataId;
        
        //read core #
        for( temp = ""; current[pos] !=',';pos++)
            temp+= current[pos];
        core = atoi(temp.c_str());
        pos++;
        
        //read first ins cache for core
        for( temp = ""; current[pos] !=',';pos++)
            temp+= current[pos];
        tempInsId = atoi(temp.c_str());
        pos++;
        
        //read first ins cache for core
        for( temp = ""; current[pos] !='\0';pos++)
            temp+= current[pos];
        tempDataId = atoi(temp.c_str());
        pos++;
        
        
        coreCountCheck++;
        dataCacheList[core] = cacheArray[tempDataId];
        insCacheList[core] = cacheArray[tempInsId];
    }

if(coreCount != coreCountCheck){
    cout<<"The core count you specified does not match the number of lines for first-level cache assignment... Please check config";
    exit(234);
    }
    
    
    file.close();
    
    
}
    
    //prints a cache. called by printCacheConfigs
    
    void printCache(Cache* const cache, set<int> printedCaches, ofstream& out){
        
        if (printedCaches.find(cache->getCacheInfo().selfID)!= printedCaches.end())
            return;
        CacheInfo info = cache->getCacheInfo();
        printedCaches.insert(info.selfID);
        printCache(cache->getParent(),printedCaches,out);
        
        out <<"Cache #"<<info.selfID<<":\n";
        out <<"Address = "<<cache;
        out <<"\nParent address= "<<cache->getParent();
        out <<"\nSize = "<<info.size;
          
    }
    
    void printCacheConfigs(){
        
        ofstream out;
        out.open("outputconfig.txt");
        
        set<int> printedCaches; // keeps track of caches that have already been printed
        for (int j=0; j<coreCount; j++){
            printCache(insCacheList[j], printedCaches, out);
            printCache(dataCacheList[j], printedCaches, out);
        }
    }
    
    
    
